/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.portlet.rendering;

import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import javax.portlet.Event;
import org.apereo.portal.portlet.om.IPortletWindowId;
import org.apereo.portal.utils.ConcurrentMapUtils;

/**
 * Used to track events generated for a specific portlet window in a thread safe manner. The event
 * source adds events to the queue for a specific {@link IPortletWindowId} and the event consumer
 * can iterate over the {@link IPortletWindowId}s that have events and get a {@link Queue} of
 * un-processed events for each {@link IPortletWindowId}
 *
 * <p>This class and all data structures returned by it are thread safe
 */
public class PortletEventQueue implements Iterable<IPortletWindowId> {
    /** Queue of portlet events that have not been resolved to target specific portlet windows */
    private final Queue<QueuedEvent> rawEventQueue = new ConcurrentLinkedQueue<QueuedEvent>();

    /**
     * Map for portlet windows where the value is a queue of events that need to be dispatched to
     * the portlet
     */
    private final ConcurrentMap<IPortletWindowId, Queue<QueuedEvent>> resolvedEventQueues =
            new ConcurrentHashMap<IPortletWindowId, Queue<QueuedEvent>>();

    PortletEventQueue() {
        // Only allow code in the same package to create the queue
    }

    /** Add the collection of events to the raw event queue */
    public void addEvents(Collection<? extends QueuedEvent> events) {
        this.rawEventQueue.addAll(events);
    }

    /** @return The Queue of unresolved events */
    public Queue<QueuedEvent> getUnresolvedEvents() {
        return this.rawEventQueue;
    }

    /** Queue an {@link Event} for the specified {@link IPortletWindowId} */
    public void offerEvent(IPortletWindowId portletWindowId, QueuedEvent event) {
        Queue<QueuedEvent> events = resolvedEventQueues.get(portletWindowId);
        if (events == null) {
            events =
                    ConcurrentMapUtils.putIfAbsent(
                            resolvedEventQueues,
                            portletWindowId,
                            new ConcurrentLinkedQueue<QueuedEvent>());
        }

        events.offer(event);
    }

    /** Remove the oldest {@link Event} from the Queue for the specified {@link IPortletWindowId} */
    public QueuedEvent pollEvent(IPortletWindowId portletWindowId) {
        final Queue<QueuedEvent> queue = this.resolvedEventQueues.get(portletWindowId);
        return queue != null ? queue.poll() : null;
    }

    /** Get an {@link Iterator} of all {@link IPortletWindowId}s that have {@link Event}s queued. */
    @Override
    public Iterator<IPortletWindowId> iterator() {
        return this.resolvedEventQueues.keySet().iterator();
    }
}
